﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using SmartProject.Data;

using NHibernate;
using NHibernate.Criterion;

namespace SmartProject
{
    public delegate void EntityDeactivate(ISession session);
    public delegate void EntityDeleted(ISession session);

    /// <summary>
    /// This is the base class all database objects inherit from. It contains the common
    /// properties that all base database objects require.
    /// </summary>
    public abstract class EntityBase
    {
        public enum Operations
        {
            None,
            Save,
            Remove,
            Delete
        }

        protected virtual event EntityDeactivate OnEntityDeactivate;
        protected virtual event EntityDeleted OnEntityDeleted;

        private Operations _operation = Operations.None;
        private readonly List<BusinessRule> _businessRules = new List<BusinessRule>();
        private readonly List<BusinessRulePreference> _businessRulePreferences = new List<BusinessRulePreference>();
        private readonly Dictionary<string, object> _businessRuleParameters = new Dictionary<string, object>();

        /// <summary>
        /// Creates a new instance of the entity base.
        /// </summary>
        public EntityBase()
        {
            Created = DateTime.Now;
            Active = true;
        }

        /// <summary>
        /// Gets or sets the name of the current operation.
        /// </summary>
        public virtual Operations GetOperation()
        {
            return _operation;
        }

        protected virtual void Add(BusinessRule businessRule)
        {
            _businessRules.Add(businessRule);
        }

        protected virtual void Add(BusinessRulePreference businessRulePreference)
        {
            _businessRulePreferences.Add(businessRulePreference);
        }

        /// <summary>
        /// Adds a required business rule parameter to be evaluated.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        public virtual void AddBusinessRuleParameter(string key, object value)
        {
            _businessRuleParameters.Add(key, value);
        }

        /// <summary>
        /// Gets a business rule parameter value. This is used only in the executation of
        /// the business rule.
        /// </summary>
        /// <param name="name">Name of the business rule parameter value.</param>
        /// <returns>Object value instance.</returns>
        public virtual object GetBusinessRuleParameterValue(string key)
        {
            return _businessRuleParameters[key];
        }

        /// <summary>
        /// Runs the business rules associated with this entity during a Save, Update, Remove or Delete.
        /// </summary>
        private void RunBusinessRules(ISession session)
        {
            if (_businessRulePreferences != null)
            {
                foreach (BusinessRulePreference preference in _businessRulePreferences)
                {
                    BusinessRule businessRule = preference.GetSelectedBusinessRule(session);
                    if (businessRule != null)
                    {
                        businessRule.Implement(session, this);
                    }
                }
            }

            if (_businessRules != null)
            {
                foreach (BusinessRule businessRule in _businessRules)
                {
                    businessRule.Implement(session, this);
                }
            }
        }

        /// <summary>
        /// Gets or sets the Id of this database object. This is configured in the Mapper 
        /// implementation with the object name followed by Id. For example, the Task class
        /// would configure the Id to be TaskId.
        /// </summary>
        public virtual int Id { get; private set; }

        /// <summary>
        /// Gets or sets the date/time that this database object is created.
        /// </summary>
        public virtual DateTime Created { get; set; }

        /// <summary>
        /// Gets or sets whether this database object is active.
        /// </summary>
        public virtual bool Active { get; set; }

        #region Helper methods to Save, Remove or Delete
        public virtual void Save(ISession session)
        {
            _operation = Operations.Save;
            RunBusinessRules(session);
            if (this.Id == 0)
            {
                this.Created = DateTime.Now;
                this.Active = true;
            }
            session.SaveOrUpdate(this);
            _operation = Operations.None;
        }

        public virtual void Remove(ISession session)
        {
            _operation = Operations.Remove;
            RunBusinessRules(session);
            this.Active = false;
            session.SaveOrUpdate(this);
            _operation = Operations.None;

            if (OnEntityDeactivate != null)
            {
                OnEntityDeactivate(session);
            }
        }

        /// <summary>
        /// Deletes the EntityBase instance permenantly.
        /// </summary>
        /// <param name="session">NHibernate session.</param>
        public virtual void Delete(ISession session)
        {
            if (this.OnEntityDeleted != null)
            {
                this.OnEntityDeleted(session);
            }
            session.Delete(this);
        }

        /// <summary>
        /// Deletes all inactive records of the type defined.
        /// </summary>
        /// <param name="session">NHibernate session.</param>
        /// <param name="type">Type to delete.</param>
        internal static void Delete(ISession session, Type type)
        {
            IList<EntityBase> entities = Find(session, null, null, type, false);
            if (entities != null)
            {
                for (int i = 0; i < entities.Count; i++)
                {
                    entities[i].Delete(session);
                }
            }
        }
        #endregion

        #region Helper method to Find

        /// <summary>
        /// Query for entities.
        /// </summary>
        /// <param name="session">Session used in this request.</param>
        /// <param name="expressions">Conditions.</param>
        /// <param name="orderings">Order your query.</param>
        /// <param name="t">Type of entity base that you are doing your query on.</param>
        /// <returns>Null if nothing can be found.</returns>
        public static IList<EntityBase> Find(ISession session, IList<ICriterion> expressions, IList<Order> orderings, Type t)
        {
            return Find(session, expressions, orderings, t, true);
        }

        /// <summary>
        /// Query for entities.
        /// </summary>
        /// <param name="session">Session used in this request.</param>
        /// <param name="expressions"></param>
        /// <param name="orderings">Conditions.</param>
        /// <param name="t">Type of entity base that you are doing your query on.</param>
        /// <param name="onlyActive">True to return only active entities from the query.</param>
        /// <returns>Null if nothing can be found.</returns>
        protected static IList<EntityBase> Find(ISession session, IList<ICriterion> expressions, IList<Order> orderings, Type t, bool onlyActive)
        {
            IList<EntityBase> entities = null;

            ICriteria criteria = session.CreateCriteria(t);
            if (expressions != null)
            {
                for (int i = 0; i < expressions.Count; i++)
                {
                    criteria = criteria.Add(expressions[i]);
                }
            }

            if (onlyActive)
            {
                // Get only active records.
                criteria.Add(Restrictions.Eq("Active", true));
            }
            else
            {
                criteria.Add(Restrictions.Eq("Active", false));
            }

            if (orderings != null)
            {
                for (int i = 0; i < orderings.Count; i++)
                {
                    criteria = criteria.AddOrder(orderings[i]);
                }
            }
            entities = criteria.List<EntityBase>().ToList();
            return entities;
        }
        #endregion

        /// <summary>
        /// Gets a count of how many active records are of this type.
        /// </summary>
        /// <returns>Integer count.</returns>
        internal static int GetCount(ISession session, Type type)
        {
            int count = -1;
            ICriteria criteria = session.CreateCriteria(type);
            // Gets only active records.
            criteria.Add(Restrictions.Eq("Active", true));
            IList<EntityBase> entities = criteria.List<EntityBase>();
            if (entities != null)
            {
                count = entities.Count;
            }
            else
            {
                count = 0;
            }
            return count;
        }

        /// <summary>
        /// Gets an entity by a known Id value.
        /// </summary>
        /// <param name="session">Session used in this request.</param>
        /// <param name="id">Known Id value.</param>
        /// <param name="type">Type to lookup.</param>
        /// <returns>EntityBase instance.</returns>
        internal static EntityBase GetEntity(ISession session, int id, Type type)
        {
            IList<ICriterion> expressions = new List<ICriterion>();
            expressions.Add(Restrictions.Eq("Id", id));
            IList<EntityBase> entities = Find(session, expressions, null, type);
            if (entities != null && entities.Count == 1)
            {
                return entities[0];
            }
            return null;
        }
    }
}
